﻿/*
 * Created by SharpDevelop.
 * User: Ja
 * Date: 8/31/2011
 * Time: 7:55 PM
 * 
 * To change this template use Tools | Options | Coding | Edit Standard Headers.
 */
using System;
using System.Drawing;
using System.Drawing.Drawing2D;

using System.Windows.Forms;
using System.Collections.Generic;
using System.Diagnostics;

namespace bminterface
{
	/// <summary>
	/// Description of Graph.
	/// </summary>
	public class Graph : PictureBox
	{
		//This calss is used to return pair of points defining the 2-D range
		private class GraphRange
		{
			public PointF min, max;
			
			public GraphRange()
			{
				reset();
			}	
			
			public void updateRange( PointF point )
			{
				Debug.Assert( point.X >=0.0f, "updateRange: point.X lower than 0.0f");
				if(	point.X > max.X ) max.X = point.X;
				if( point.X < min.X ) min.X = point.X;
				if( point.Y > max.Y ) max.Y = point.Y;
				if( point.Y <min.Y ) min.Y = point.Y;
			}
			
			public float width()
			{
				return max.X - min.X;
			}
			public float height()
			{
				return max.Y - min.Y;
			}
			public void reset()
			{
				max = new PointF( float.MinValue, float.MinValue );
				min = new PointF( float.MaxValue, float.MaxValue );
			}
		}
		
		public class Curve
		{	
			int MAX_POINTS = 10000;
			List<PointF> points;
			Color color;
			
			public Curve( Color col)
			{
				points = new List<PointF>();
				color = col;
			}
			
			public bool addPoint(PointF point)
			{
				if( points.Count > 0 )
					Debug.Assert( point.X >= points[points.Count-1].X , "addPoint: new point is not next in order" );
				if( points.Count > MAX_POINTS )
				{
					points.RemoveAt(0);
					points.Add( point );
					return false;
				}
				
				points.Add( point );
				
				return true;
			}
			
			public void replacePointList( List<PointF> pointList )
			{
				points = pointList;
			}
			
			public List<PointF> getPoints()
			{
				return points;
			}
			
			public int pointCount()
			{
				return points.Count;
			}
			public Color getColor()
			{
				return color;
			}
		}
		
		
		SolidBrush brush;
		Bitmap backBuffer, frontBuffer; // Only backBuffer should be drawn.
		
		List<Curve> curves;
		Color []brushColors;
        int curveCount = 0;
        int MAX_CURVES = 10;
		int MAX_BRUSH_COLORS = 6;
		
		GraphRange m_currRange; // This range may change after adding another point to the chart
		float HORIZONTAL_MARGIN;
		float VERTICAL_MARGIN;
		
		// This are scaling factors which are used to translate true points' coordinates to
		// graph-size dependant coordinates.
		float scaleFactorX = 1.0f;
		float scaleFactorY = 1.0f;
		
		// This variables are used to determine spanning between grid lines.
		const float GRID_DISTANCE_H = 60.0f;
		const float GRID_DISTANCE_V = 80.0f;
		
		public Graph()
		{
			this.BackColor = Color.White;
			//this.BorderStyle = BorderStyle.FixedSingle;
			//this.SetStyle( ControlStyles.DoubleBuffer, true );
			//this.SetStyle( ControlStyles.OptimizedDoubleBuffer, true );
			brush = new SolidBrush( Color.White );
			
			// Initialize colors;
			brushColors = new Color[MAX_BRUSH_COLORS];
			
            brushColors[0] = Color.Black;
			brushColors[1] = Color.Blue;
			brushColors[2] = Color.Red;
			brushColors[3] = Color.Plum;
			brushColors[4] = Color.Gold;
			brushColors[5] = Color.Brown;
			
			Curve c1 = new Curve(Color.Blue);
			curves = new List<Graph.Curve>();
			
			curves.Add( c1 );
			curves.Add( new Curve(Color.Red));
		}
		
		// This procedure checks the range in points and stores the result in currRange
		private bool checkRange()
		{
			bool result = false;
			
			m_currRange.reset();
			foreach( Curve curve in curves )
			{
				foreach( PointF point in curve.getPoints() )
				{
					m_currRange.updateRange( point );
					result = true;
				}
			}
			return result;
		}

		public void draw()
		{
			if( this.Width > 0 && this.Height > 0 )
			{
				Graphics graphics = this.CreateGraphics();
			
				// Create new Bitmap to store the new image
				backBuffer = new Bitmap( this.Width, this.Height );
				Graphics graphicsBMP = Graphics.FromImage( backBuffer );
				
				m_currRange = new GraphRange();
				
				// Check is needed to properly scale axes and curves
				if( (checkRange() == true ) && (m_currRange.width() != 0.0f) && (m_currRange.height() != 0.0f) )
				{
					// Compute the scaling factors to fit the curves into the Graph pane
					HORIZONTAL_MARGIN = 0.1f*this.Width; // This values may change due to control resizing and
					VERTICAL_MARGIN = 0.05f*this.Height; // should be checked each time the control is beeing re-drawn.

					scaleFactorX = ( this.Width  - 2*HORIZONTAL_MARGIN ) / m_currRange.width();
					scaleFactorY = ( this.Height - 2*VERTICAL_MARGIN ) / m_currRange.height();
					graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
					drawBackground( ref graphicsBMP );
					drawGrid( ref graphicsBMP );
					drawAxesDescription( ref graphicsBMP );
					drawCurves( ref graphicsBMP );
					
					swapBuffers();
					
					graphics.DrawImage( frontBuffer, 0, 0 );
				}
			}
		}
		
		private void swapBuffers()
		{
			Bitmap temp;
			temp = frontBuffer;
			frontBuffer = backBuffer;
			backBuffer = temp;
		}
		
		private void drawBackground( ref Graphics graphics )
		{
			Debug.Assert( graphics != null, "Graph.drawBackground(): Invalid Graphics  object" );
			Pen pen = new Pen( brushColors[0] );
			graphics.Clear( Color.White);
			
			// top margin
			graphics.DrawLine( pen, HORIZONTAL_MARGIN, VERTICAL_MARGIN, this.Width - HORIZONTAL_MARGIN, VERTICAL_MARGIN );
			// left margin
			graphics.DrawLine( pen, HORIZONTAL_MARGIN, VERTICAL_MARGIN, HORIZONTAL_MARGIN, this.Height - VERTICAL_MARGIN );
			// right margin
			graphics.DrawLine( pen, this.Width - HORIZONTAL_MARGIN, VERTICAL_MARGIN, this.Width - HORIZONTAL_MARGIN, this.Height - VERTICAL_MARGIN );
			// bottom margin
			graphics.DrawLine( pen, HORIZONTAL_MARGIN, this.Height - VERTICAL_MARGIN, this.Width - HORIZONTAL_MARGIN,  this.Height -VERTICAL_MARGIN );
		}
		
		private void drawGrid( ref Graphics graphics )
		{
			Debug.Assert( graphics != null, "Graph.drawGrid(): Invalid Graphics  object" );
			
			float currWidth = GRID_DISTANCE_H + HORIZONTAL_MARGIN;
			float currHeight = GRID_DISTANCE_V;
			
			float driftOffset =  0;
			
			Pen pen = new Pen( Color.Gray );
			
			// Drawing moving grid lines.
			driftOffset = (m_currRange.min.X*scaleFactorX) % GRID_DISTANCE_H ;
			while( currWidth < this.Width - HORIZONTAL_MARGIN )
			{	
				graphics.DrawLine( pen, currWidth - driftOffset ,VERTICAL_MARGIN, currWidth - driftOffset,  this.Height - VERTICAL_MARGIN);
				currWidth += GRID_DISTANCE_H;		
			}
			
			float zero_pos = 0;
			if( m_currRange.max.Y > 0.0f && m_currRange.min.Y < 0.0f )
			{
				zero_pos = ((float)this.Height - 2.0f*VERTICAL_MARGIN ) * ( m_currRange.max.Y / (m_currRange.max.Y - m_currRange.min.Y ));
				pen.Color = Color.Black;
				graphics.DrawLine( pen , HORIZONTAL_MARGIN, zero_pos  + VERTICAL_MARGIN, this.Width - HORIZONTAL_MARGIN, zero_pos  + VERTICAL_MARGIN);
			}
			
			// Drawing horizontal lines
			pen.Color = Color.Gray;
			currHeight = zero_pos +  VERTICAL_MARGIN +  GRID_DISTANCE_V;
			while( currHeight < this.Height - VERTICAL_MARGIN )
			{		
				graphics.DrawLine( pen, HORIZONTAL_MARGIN, currHeight, this.Width - HORIZONTAL_MARGIN, currHeight);
				currHeight += GRID_DISTANCE_V;																									
			}
			
			currHeight = zero_pos +  VERTICAL_MARGIN -  GRID_DISTANCE_V;
			while( currHeight > VERTICAL_MARGIN )
			{		
				graphics.DrawLine( pen, HORIZONTAL_MARGIN, currHeight, this.Width - HORIZONTAL_MARGIN, currHeight);
				currHeight -= GRID_DISTANCE_V;																									
			}
			
		}
		
		private void drawAxesDescription( ref Graphics graphics )
		{
			Debug.Assert( graphics != null, "Graph.drawAxesDescription(): Invalid Graphics  object" );
			float GRID_DISTANCE_H = 60.0f;
			float GRID_DISTANCE_V = 80.0f;
			
			float currHorPos = GRID_DISTANCE_H + HORIZONTAL_MARGIN;
			float currVertPos = GRID_DISTANCE_V;
			
			float driftOffset =  0;
			
			Pen pen = new Pen( Color.Blue );
			Brush brush = new SolidBrush( Color.Blue );
			Font font = new Font("Arial", VERTICAL_MARGIN/2);
			
			graphics.DrawString( "t[s] ", font, brush, this.Width - HORIZONTAL_MARGIN, this.Height - VERTICAL_MARGIN);
			graphics.DrawString( "V[mV] ", font, brush, 0.0f , 0.0f);
			
			// X axis
			driftOffset = (m_currRange.min.X*scaleFactorX) % GRID_DISTANCE_H ;
			float gridSpan = m_currRange.max.X - m_currRange.min.X;
			float currValue;
			string str;
			
			font = new Font("Arial",  VERTICAL_MARGIN/2 );
			while( currHorPos < this.Width - HORIZONTAL_MARGIN )
			{		
				currValue = (currHorPos - HORIZONTAL_MARGIN )/ (this.Width - 2*HORIZONTAL_MARGIN) * gridSpan + m_currRange.min.X;
				str = currValue.ToString("N4");
				
				graphics.DrawString( str, font, brush, currHorPos - driftOffset - GRID_DISTANCE_H/2, this.Height - VERTICAL_MARGIN);
				currHorPos += 2*GRID_DISTANCE_H;	
			}
			
			while( currHorPos < this.Height - VERTICAL_MARGIN )
			{		
				currValue = (currHorPos - HORIZONTAL_MARGIN )/ (this.Width - 2*HORIZONTAL_MARGIN) * gridSpan + m_currRange.min.X;
				
				graphics.DrawString( (currValue*0.001f).ToString("N4"), font, brush, currHorPos - driftOffset - GRID_DISTANCE_H/2, this.Height - VERTICAL_MARGIN);
				currHorPos += 2*GRID_DISTANCE_H;	
			}
			
			
			// Compute position of zero line;
			float zero_pos = 0;
			if( m_currRange.max.Y >= 0.0f && m_currRange.min.Y <= 0.0f )
			{
				zero_pos = ((float)this.Height - 2.0f*VERTICAL_MARGIN ) * ( m_currRange.max.Y / (m_currRange.max.Y - m_currRange.min.Y ));
			}
			
			pen.Color = Color.Gray;
			currVertPos = zero_pos +  VERTICAL_MARGIN +  GRID_DISTANCE_V;
			gridSpan = m_currRange.max.Y - m_currRange.min.Y;
			
				
			while( currVertPos < this.Height - VERTICAL_MARGIN )
			{	
				//currValue = (currVertPos - VERTICAL_MARGIN ) / (this.Height - 2*VERTICAL_MARGIN ) * gridSpan + m_currRange.min.Y;
				currValue = m_currRange.max.Y - (currVertPos - VERTICAL_MARGIN ) / (this.Height - 2*VERTICAL_MARGIN ) * gridSpan;
			
				
				graphics.DrawString( currValue.ToString("N4"), font, brush, 0.0f, currVertPos );
				currVertPos += 2*GRID_DISTANCE_V;																									
			}
			
			currVertPos = zero_pos +  VERTICAL_MARGIN -  GRID_DISTANCE_V;
			
			while( currVertPos > VERTICAL_MARGIN )
			{
				currValue = m_currRange.max.Y - (currVertPos - VERTICAL_MARGIN ) / (this.Height - 2*VERTICAL_MARGIN ) * gridSpan;
			
				// currValue = gridSpan/(this.Height - 2.0f*VERTICAL_MARGIN)
				graphics.DrawString( currValue.ToString("N4"), font, brush, 0.0f, currVertPos );
				currVertPos -= 2*GRID_DISTANCE_V;																									
			}
			
		}
			
		private void drawCurves( ref Graphics graphics )
		{
			Debug.Assert( graphics != null, "Graph.drawCurves(): Invalid Graphics  object" );
			int currColor = 0;
			PointF startPoint;
			Pen pen;
			foreach( Curve curve in curves )
			{
				if( curve.pointCount() > 0 )
				{
					Debug.Assert( currColor < MAX_BRUSH_COLORS );
					//currColor++;
					brush.Color = curve.getColor();
					pen = new Pen( brush );
					
					startPoint = curve.getPoints()[0];
					
					foreach( PointF endPoint in curve.getPoints() )
					{
						if( endPoint != startPoint )
						{
							graphics.DrawLine( pen, 
							                   (startPoint.X - m_currRange.min.X)* scaleFactorX + HORIZONTAL_MARGIN ,
							                   this.Height - ( startPoint.Y - m_currRange.min.Y ) *scaleFactorY - VERTICAL_MARGIN,
										       (endPoint.X - m_currRange.min.X)*scaleFactorX + HORIZONTAL_MARGIN,
										       this.Height - ( endPoint.Y - m_currRange.min.Y )*scaleFactorY - VERTICAL_MARGIN
										      );
							startPoint = endPoint; 
						}
					}
				}
				
			}
		}

        // Creates new curve and returns it's handle
        public int createCurve( Color color )
        {
            if (curveCount < MAX_CURVES)
            {                
                curves.Add(new Curve( color));
                curveCount++;
                brushColors[curveCount] = color;

                return curveCount - 1; // return correct handle
            }

            return -1; // max number of curves reached
        }

        public bool removeCurve( Color color )
        {
        	Curve tmp = null;
        	foreach( Curve c in curves )
        	{
        		if( c.getColor() == color) tmp = c;
        	}
        	
        	if( tmp != null )
        	{
        		curves.Remove( tmp );
        		return true;
        	}
        	return false;
        }
        public void addPointToCurve(float x, float y, int handle)
        {
            if( handle > 0)
            {
                curves[handle].addPoint(new PointF(x, y));
            }
        }

        public void addPointListToCurve(List<PointF> points, int handle)
        {
            if( handle >= 0)
            {
                foreach (PointF p in points)
                {
                    curves[handle].addPoint( p );
                }
            }
        }

		public void testAddRandom(float x, float y)
		{
			curves[0].addPoint(new PointF(x, y));
		}
		public void testAddRandom2(float x, float y)
		{
			curves[1].addPoint(new PointF(x, y));
		}
		
		public void testReplace(List<PointF> pointList )
		{
			curves[1].replacePointList( pointList );
		}
	}
}
